home *** CD-ROM | disk | FTP | other *** search
/ EnigmA Amiga Run 1996 June / EnigmA AMIGA RUN 08 (1996)(G.R. Edizioni)(IT)[!][issue 1996-06][EARSAN CD VII].iso / earcd / cmdity / clicker.lha / Clicker / Source / sound.c < prev    next >
C/C++ Source or Header  |  1996-05-17  |  8KB  |  255 lines

  1. /*  File:         sound.c
  2.  *  Created:      20-10-95
  3.  *  Updated:      17-05-96
  4.  *  Version:      1.3
  5.  *  Project:      Clicker
  6.  *  Owner:        Jeroen Vermeulen
  7.  *  Requirements: KickStart V39+
  8.  *  Legal:        PD
  9.  *  Status:       Release
  10.  */
  11.  
  12. #include <math.h>
  13.  
  14. #include <proto/exec.h>
  15. #include <exec/devices.h>
  16. #include <exec/memory.h>
  17. #include <devices/audio.h>
  18. #include <proto/alib.h>
  19.  
  20. #include "sample.h"
  21. #include "main.h"
  22. #include "sound.h"
  23. #include "prefs.h"
  24.  
  25.  
  26. static STRPTR
  27.         AllocFailPubMem  = "Not enough PUBLIC memory!\n",
  28.         OpenFailAudioDev = "Can't open audio.device\n";
  29.  
  30.  
  31. /* Allocation priority array for sound channels.  I don't much care which one
  32.  * we get; we only need one.
  33.  */
  34. static unsigned char SoundChannels[] = { 1,2,4,8 };
  35.  
  36.  
  37. /* CreateSample():
  38.  * Open audio device and sample.  An IOAudio structure is returned unless either
  39.  * an error occurs or the error string was already non-NULL before the call.
  40.  * ClickPrefs must also be properly initialized before KeyClick() can be called.
  41.  */
  42. struct IOAudio *CreateSample(STRPTR *const error)
  43. {
  44.   struct IOAudio   *soundrequest = NULL;
  45.   struct IORequest *plainrequest;
  46.   struct MsgPort   *Reply;
  47.  
  48.   if ((Reply = CreateMsgPort()))
  49.   {
  50.     plainrequest = CreateIORequest(Reply,sizeof(struct IOAudio));
  51.     soundrequest = (struct IOAudio *)plainrequest;
  52.  
  53.     if (soundrequest)
  54.     {
  55.       if (OpenDevice("audio.device",0,plainrequest,0) == 0)
  56.       {
  57.         /* We've succeeded in opening the audio.device, which is all that
  58.          * matters right now.  We'll try to allocate a sound channel, but
  59.          * KeyClick() will know what to do if we fail.
  60.          */
  61.         plainrequest->io_Message.mn_Node.ln_Pri = -128; /* Never steal channels */
  62.         plainrequest->io_Command = ADCMD_ALLOCATE;
  63.         plainrequest->io_Flags = ADIOF_NOWAIT;
  64.         soundrequest->ioa_Data = SoundChannels;
  65.         soundrequest->ioa_Length = sizeof(SoundChannels);
  66.         BeginIO(plainrequest);
  67.         WaitIO(plainrequest);
  68.  
  69.         /* Now set up for first click!  To think I forgot this once and have
  70.          * been after the bug for weeks...
  71.          * The lesson is:  Never assume you're not stupid enough for something.
  72.          * (I know what you're thinking: "I may be stupid but I'm not stupid
  73.          *  enough to make _that_ assumption")
  74.          */
  75.         plainrequest->io_Command = CMD_WRITE;
  76.         plainrequest->io_Flags = ADIOF_PERVOL;
  77.         soundrequest->ioa_Data = (signed char *)Sample;
  78.         soundrequest->ioa_Length = SAMPLELENGTH;
  79.       }
  80.       else *error = OpenFailAudioDev;
  81.     }
  82.     else *error = AllocFailPubMem;
  83.   }
  84.   else *error = AllocFailMsgPort;
  85.  
  86.   if (*error)
  87.   {
  88.     DeleteSample(soundrequest);
  89.     soundrequest = NULL;
  90.   }
  91.  
  92.   return soundrequest;
  93. }
  94.  
  95.  
  96. /* Destroy sample and free all resources allocated with it.  This function is
  97.  * overly robust so it can be used from within CreateSample() in case of an
  98.  * error.
  99.  */
  100. void DeleteSample(struct IOAudio *const soundrequest)
  101. {
  102.   if (soundrequest)
  103.   {
  104.     struct IORequest *const plainrequest = &soundrequest->ioa_Request;
  105.     struct MsgPort   *const ReplyPort = plainrequest->io_Message.mn_ReplyPort;
  106.     const struct Device *const io_Device = plainrequest->io_Device;
  107.     /* --- */
  108.     if (io_Device && ((long)io_Device != -1)) CloseDevice(plainrequest);
  109.     DeleteIORequest(plainrequest);
  110.     if (ReplyPort) DeleteMsgPort(ReplyPort);
  111.   }
  112. }
  113.  
  114.  
  115. /* KeyClick():
  116.  * Make key-click noise.  The soundrequest pointer is assumed to be valid and
  117.  * non-NULL, and point at a properly initialized IOAudio structure.
  118.  * The IOAudio structure must be set up for a WRITE request; this is its
  119.  * default state to which it is also restored at the end of the function.
  120.  * This function accesses ClickPrefs; make sure it is set up properly (and set
  121.  * its "newsettings" flag for the initial call).
  122.  *
  123.  * If the previous click failed due to the channel being stolen, we attempt to
  124.  * allocate a new channel.
  125.  */
  126. void KeyClick(struct IOAudio *const soundrequest)
  127. {
  128.   struct IORequest *const plainrequest = &soundrequest->ioa_Request;
  129.   /* --- */
  130.  
  131.   /* Allocate channel if necessary */
  132.   if (plainrequest->io_Error) /* Channel stolen or allocation failed */
  133.   {
  134.     plainrequest->io_Error = 0;
  135.     plainrequest->io_Flags = ADIOF_NOWAIT;
  136.     plainrequest->io_Command = ADCMD_ALLOCATE;
  137.     soundrequest->ioa_Data = SoundChannels;
  138.     soundrequest->ioa_Length = sizeof(SoundChannels);
  139.     soundrequest->ioa_AllocKey = 0;
  140.  
  141.     BeginIO(plainrequest);
  142.  
  143.     /* This line is in a strange place, admittedly.  I put it here just so I
  144.      * might catch a little low-level parallellism on the off-chance.
  145.      * If I have to do my audio I/O asynchronously I expect to be able to
  146.      * exploit some of that somewhere.  This is my chance!
  147.      */
  148.     ClickPrefs.newsettings = TRUE; /* Reload sound settings before click */
  149.  
  150.     WaitIO(plainrequest);
  151.     if (plainrequest->io_Error) return;
  152.  
  153.     /* Set up for click */
  154.     plainrequest->io_Command = CMD_WRITE;
  155.     soundrequest->ioa_Data = (signed char *)Sample;
  156.     soundrequest->ioa_Length = SAMPLELENGTH;
  157.   }
  158.  
  159.   /* Prefs */
  160.   if (ClickPrefs.newsettings)
  161.   {
  162.     ClickPrefs.newsettings = FALSE;
  163.     soundrequest->ioa_Period = ClickPrefs.period;
  164.     soundrequest->ioa_Volume = ClickPrefs.volume;
  165.     soundrequest->ioa_Cycles = ClickPrefs.cycles;
  166.     plainrequest->io_Flags = ADIOF_PERVOL;
  167.   }
  168.  
  169.   /* Click */
  170.   BeginIO(plainrequest);
  171.   WaitIO(plainrequest);
  172.  
  173.   /* Sound request is ready for a new write command on exit */
  174. }
  175.  
  176.  
  177. /* SliderToHertz():
  178.  * Converts a prefs window slider position (between -5*12 and 4*12) to a
  179.  * frequency in Hertz, based on a twelve-tone octave centered at the 440 Hz A.
  180.  */
  181. LONG SliderToHertz(const struct Gadget *const dum, const WORD sliderpos)
  182. {
  183.   /* Frequency is 440 * 2^(n/12).  Pity we can't use left-shift here!
  184.    */
  185.   return (LONG)(440.0 * pow(2.0,sliderpos/12.0));
  186. }
  187.  
  188.  
  189. /* HertzToPeriod():
  190.  * Converts human-readable pitch in Hertz to period length suitable for use by
  191.  * audio.device.  As a rule, HertzToPeriod(SliderToHertz(S)) is equivalent to
  192.  * SliderToPeriod(S).
  193.  */
  194. UWORD HertzToPeriod(const LONG Hertz)
  195. {
  196.   /* Period should be computed from frequency f as 10^9 / (279.365 * s * f),
  197.    * where s is the sample length.  With proper constant folding, this should
  198.    * compile to (C/Hertz) where C is a constant.
  199.    */
  200.   return (UWORD)((1000000000.0/(279.365*SAMPLELENGTH))/Hertz);
  201. }
  202.  
  203.  
  204. /* PeriodToHertz():
  205.  * Converts audio.device period length to human-readable pitch in Hertz.  This
  206.  * is the inverse of HertzToPeriod().
  207.  */
  208. LONG PeriodToHertz(const UWORD period)
  209. {
  210.   /* Pitch in Hertz is computed from period f as 279.365 * s * p / 10^9, where s
  211.    * is sample length.
  212.    */
  213.   return (LONG)((279.365 * SAMPLELENGTH / 1000000000.0) * period);
  214. }
  215.  
  216.  
  217. /* SliderToPeriod():
  218.  * Converts a prefs window slider position (between 0 and 9*12) to a period
  219.  * length (in units of 279.365 nanoseconds) suitable for use by audio.device.
  220.  */
  221. UWORD SliderToPeriod(const WORD sliderpos)
  222. {
  223.   /* For a frequency f, the period length is 10^9 / (279.365 * s * f).  Here s
  224.    * is the length of the waveform sample (SAMPLELENGTH).
  225.    * So we need to compute 10^9 / (279.365 * s * (440 * 2^(n/12)))
  226.    * == 2^(-n/12) * 10^9 / (279.365 * 440 * s)
  227.    * ==  2^(-n/12) * 10^9 / (122920.6 * s)
  228.    */
  229.   return (UWORD)(pow(2.0,-(double)sliderpos/12.0) *
  230.                 (10000000.0 / (1229.206 * SAMPLELENGTH)));
  231. }
  232.  
  233.  
  234. /* PeriodToSlider():
  235.  * Inverse of SliderToPeriod().  Takes a period length as an argument and
  236.  * computes the appropriate slider position (between -5*12 and 4*12).  This
  237.  * function can afford to be slow because it's only ever called when the prefs
  238.  * window pops up.
  239.  */
  240. WORD PeriodToSlider(const UWORD period)
  241. {
  242.   /* Since the period number p can be computed from slider position n by
  243.    *  p = 10^9 / (279.365 * 440 * s * 2^(n/12))  <==>
  244.    *  2^(n/12) = 10^9 / (279.365 * 440 * s * p)  <==>
  245.    *  n/12 = 2log(10^9 / (279.365 * 440 * s * p)) <==>
  246.    *  n = 12 * 2log(10^9 / (279.365 * 440 * s * p))  ==
  247.    *      12 * 2log(10^9 / (122920.6 * s * p))  ==
  248.    *      12 * ln(10^9 /  (122920.6 * s * p)) / ln(2)
  249.    */
  250.   return (WORD)(12.0 * log(
  251.                 1000000000.0 / (122920.6*SAMPLELENGTH*period)
  252.                )/log(2.0));
  253. }
  254.  
  255.